package com.googlecode.cswish.struts.spring;

import javax.servlet.ServletContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.StrutsConstants;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;

import com.googlecode.cswish.struts.StartupManager;
import com.googlecode.cswish.util.FrameConstant;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.spring.SpringObjectFactory;

public class StrutsObjectFactory extends SpringObjectFactory {

	private static final Log logger = LogFactory.getLog(ObjectFactory.class);
	
	// FIXME: Remove this flag after finding the reason of twice initializations
	private static boolean first = true;

	@Inject
	public StrutsObjectFactory(@Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE,required=false) String autoWire,
			@Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_USE_CLASS_CACHE,required=false) String useClassCacheStr,
			@Inject ServletContext servletContext) {		
		logger.info("Initializing Struts-Spring integration...");
		
		if (servletContext != null) {
			appContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
			
			if (appContext == null && logger.isInfoEnabled()) {
	            // uh oh! looks like the lifecycle listener wasn't installed. Let's inform the user
	            String message = "********** FATAL ERROR STARTING UP SPRING-STRUTS INTEGRATION **********\n" +
	                    "Looks like the Spring listener was not configured for your web app! \n" +
	                    "Nothing will work until WebApplicationContextUtils returns a valid ApplicationContext.\n" +
	                    "You might need to add the following to web.xml: \n" +
	                    "    <listener>\n" +
	                    "        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>\n" +
	                    "    </listener>";
	            logger.info(message);
	            logger.info("use StaticWebApplicationContext to initialize spring configuration");
			}
		} else {	// for JUnit test
			try {
				Class clazz = Class.forName("org.springframework.mock.web.MockServletContext");
				servletContext = (ServletContext) clazz.newInstance();
			} catch (Exception ex) {
				logger.error("Failed to load class", ex);
			}
		}
        if (appContext == null) {	// for JUnit test
        	System.setProperty(SystemEnv.JUNIT_TEST, "true");
        	SystemEnv.init();
        	XmlWebApplicationContext webAppContext = new XmlWebApplicationContext();
        	webAppContext.setServletContext(servletContext);
       		webAppContext.setConfigLocation("classpath*:/applicationContext.xml classpath*:/applicationContext-*.xml");
        	webAppContext.refresh();
    		appContext = webAppContext;
    		
    		servletContext.setAttribute(
    				WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, webAppContext);
        } else {
        	appContext = SiteApplicationContext.getInstance(appContext);
        }
       
        this.setApplicationContext(appContext);
        
        int type = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;   // default
        if ("name".equals(autoWire)) {
            type = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
        } else if ("type".equals(autoWire)) {
            type = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
        } else if ("auto".equals(autoWire)) {
            type = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;
        } else if ("constructor".equals(autoWire)) {
            type = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;
        }
        this.setAutowireStrategy(type);

		logger.info("... initialized Struts-Spring integration successfully");
		BeanDefinitionRegistry register = findRegistry(findAutoWiringBeanFactory(appContext));
		String dummyBeanName = FrameConstant.NAME + ".dummyBean";
		if (!register.containsBeanDefinition(ObjectFactory.class.getName())) {
			RootBeanDefinition dummyBeanDefinition = new RootBeanDefinition(DummyBean.class, autowireStrategy, false);
			register.registerBeanDefinition(dummyBeanName, dummyBeanDefinition);
		}
	}
	
	@Inject
    public void setContainer(Container container) {
		if (first) {
			first = false;
			
			super.setContainer(container);
	        getBean(ObjectFactory.class, false).setContainer(container);
	        getBean(StartupManager.class, true).startup(container,
	        		findRegistry(findAutoWiringBeanFactory(appContext)));
		}
    }
	
	private BeanDefinitionRegistry findRegistry(AutowireCapableBeanFactory autoWiringFactory) {
		return (BeanDefinitionRegistry) autoWiringFactory;
	}
	
	public <T> T getBean(Class<T> type, boolean autowireAndInject) {
		String[] names = this.appContext.getBeanNamesForType(type);
		if (names.length == 0) {
			return null;
		}
		
		String name = names[names.length - 1];
		Object bean = this.appContext.getBean(name);
		if (autowireAndInject) {
			autoWireBean(bean);
		}
		return (T) bean;
	}
}
